/*
 *
 * File Name: at_dbus.c
 * Author: liaoxuecheng
 * mail: liaoxuecheng@hotmail.com
 * Created Time: 2024年12月26日 星期四 10时44分26秒
 *
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <stdarg.h>
#include <pthread.h>
#include <signal.h>
#include <dbus/dbus.h>

#include "at_dbus.h"

#define THIS_FILE "at_dbus.c"

#ifndef PJ_LOG
#define TEST_PRINTF(title,fmt,arg...) printf("%s:%d %s, "fmt, __func__, __LINE__, title, ##arg)
#define PJ_LOG(arg, fmt) printf("level = %d, ",arg);TEST_PRINTF fmt;
#endif

static pthread_mutex_t *sync_mutex = NULL;
static long queue_max_size = 10 * 1024;
/**
 * @brief at_dbus_init_common_connection()
 * If you want use common connection called by dbus_bus_get() in threads
 *  You need ues this function frist
 */
void at_dbus_init_common_connection(pthread_mutex_t *mutex, long max_len)
{
    sync_mutex = mutex;
    if(max_len)
        queue_max_size = max_len;
}

/**
 * @brief variant deep copy.
 */
at_variant *at_variant_copy(at_variant *value)
{
    int i;
    if(!value)
    {
        return NULL;
    }
    at_variant *variant = (at_variant *)malloc(sizeof(at_variant));
    if(!variant)
    {
        return NULL;
    }

    memcpy(variant->type, value->type, value->n_args);
    variant->n_args = 0;
    for(i = 0; i < value->n_args; i++)
    {
        switch(value->type[i])
        {
            case 'i':
            case 'u':
                    variant->args[i] = (void *)malloc(sizeof(int));
                    if(variant->args[i] == NULL)
                        goto ERR;
                    memcpy(variant->args[i], value->args[i], sizeof(int));
                    variant->n_args++;
                break;
            case 's':
                    variant->args[i] = strdup(value->args[i]);
                    if(variant->args[i] == NULL)
                        goto ERR;
                    variant->n_args++;
                break;
            case 'd':
                    variant->args[i] = (void *)malloc(sizeof(double));
                    if(variant->args[i] == NULL)
                        goto ERR;
                    memcpy(variant->args[i], value->args[i], sizeof(double));
                    variant->n_args++;
                break;
            case 'a':
                {
                    if(value->type[i+1] != 'u')
                        goto ERR;
                    unsigned int arg_u = *((int *)value->args[i+1]);
                    if(arg_u == 0)
                    {
                        variant->args[i] = (void *)0;
                    }
                    else
                    {
                        variant->args[i] = (void *)malloc(arg_u);
                        if (variant->args[i] == NULL)
                            goto ERR;
                        memcpy(variant->args[i], value->args[i], arg_u);
                    }
                    variant->n_args++;
                    i++;
                    //----------分割线
                    variant->args[i] = (void *)malloc(sizeof(unsigned int));
                    if (variant->args[i] == NULL)
                        goto ERR;
                    *(unsigned int *)(variant->args[i]) = arg_u;
                    variant->n_args++;
                }
                break;
            default:
                goto ERR;
                break;
        }
    }
    if(variant->n_args != value->n_args)
        goto ERR;
    return variant;
ERR:
    at_variant_free(variant);
    return NULL;
}

/**
 * @brief free variant.
 */
void at_variant_free(at_variant *variant)
{
	int i;

	if(!variant){
		return ;
	}
	for(i=0; i<variant->n_args; i++) {
		free(variant->args[i]);
	}
	free(variant);
}

/**
 * @brief create a variant.
 */
at_variant *at_variant_new(const char *format_string, ...)
{
	va_list ap;
	int i, format_string_size;
	at_variant *variant;

	format_string_size = strlen(format_string);

	if(format_string_size > VARIANT_MAX_ARGS) {
		return NULL;
	}

	variant = (at_variant *)malloc(sizeof(at_variant));
	if(variant == NULL) {
		return NULL;
	}

	variant->n_args = 0;
	memset(variant->type, 0, sizeof(variant->type));
	strncpy(variant->type, format_string, sizeof(variant->type));
	memset(variant->args, 0, sizeof(variant->args));

	va_start(ap, format_string);
	for(i=0; i<format_string_size; i++) {
		switch(format_string[i]) {
			case 'i':
				{
					int arg_i;

					arg_i = va_arg(ap, int);
					variant->args[i] = (void *)malloc(sizeof(int));
					if(variant->args[i] == NULL) {
						goto ERR;
					}

					*(int *)(variant->args[i]) = arg_i;
					variant->n_args ++;
				}
				break;

			case 's':
				{
					char *arg_s;
					int size;

					arg_s = va_arg(ap, char *);
					size = strlen(arg_s) + 1;

					variant->args[i] = (void *)malloc(size);
					if(variant->args[i] == NULL) {
						goto ERR;
					}

					memset(variant->args[i], 0, size);
					strncpy(variant->args[i], arg_s, size - 1);
					variant->n_args ++;
				}
				break;

			case 'u':
				{
					unsigned int arg_u;

					arg_u = va_arg(ap, unsigned int);
					variant->args[i] = (void *)malloc(sizeof(unsigned int));
					if(variant->args[i] == NULL) {
						goto ERR;
					}

					*(unsigned int *)(variant->args[i]) = arg_u;
					variant->n_args ++;
				}
				break;

			case 'd':
				{
					double arg_d;

					arg_d = va_arg(ap, double);
					variant->args[i] = (void *)malloc(sizeof(double));
					if(variant->args[i] == NULL) {
						goto ERR;
					}

					*(double *)(variant->args[i]) = arg_d;
					variant->n_args ++;
				}
				break;
			case 'a':
				{
					unsigned int arg_u;
					char *v_array;

					if(format_string[i+1] != 'u') {
						goto ERR;
					}
					v_array = va_arg(ap, char *);
					arg_u = va_arg(ap, unsigned int);

					if(arg_u == 0) {
						variant->args[i] = (void *)0;
					}else {
						variant->args[i] = (void *) malloc(arg_u);
						if (variant->args[i] == NULL ) {
							goto ERR;
						}
						memcpy(variant->args[i], v_array, arg_u);
					}

					variant->n_args ++;
					i++;
					//----------分割线
					variant->args[i] = (void *) malloc(sizeof(unsigned int));
					if (variant->args[i] == NULL ) {
						goto ERR;
					}

					*(unsigned int *) (variant->args[i]) = arg_u;
					variant->n_args++;
				}
				break;
			default:
				{
					goto ERR;
				}
				break;
		}

	}
	va_end(ap);

	return variant;

ERR:
	va_end(ap);
	at_variant_free(variant);
	return NULL;
}

static dbus_bool_t at_variant_append_args(at_variant *variant, char type, void *value, unsigned int array_len)
{
	int cur_pos;

	if(variant->n_args >= VARIANT_MAX_ARGS) {
		return FALSE;
	}
	cur_pos = variant->n_args;
	switch(type) {
		case 'i':
			{
				variant->args[cur_pos] = (void *)malloc(sizeof(int));
				if(variant->args[cur_pos] == NULL) {
					return FALSE;
				}

				*((int *)(variant->args[cur_pos])) = *((int *)value);
				variant->type[cur_pos] = 'i';
				variant->n_args ++;
			}
			break;

		case 's':
			{
				int size;

				size = strlen(value) + 1;

				variant->args[cur_pos] = (void *)malloc(size);
				if(variant->args[cur_pos] == NULL) {
					return FALSE;
				}

				memset(variant->args[cur_pos], 0, size);
				strncpy(variant->args[cur_pos], (char *)value, size - 1);
				variant->type[cur_pos] = 's';
				variant->n_args ++;
			}
			break;

		case 'u':
			{
				variant->args[cur_pos] = (void *)malloc(sizeof(unsigned int));
				if(variant->args[cur_pos] == NULL) {
					return FALSE;
				}

				*((unsigned int *)(variant->args[cur_pos])) = *((unsigned int *)value);
				variant->type[cur_pos] = 'u';
				variant->n_args ++;
			}
			break;

		case 'd':
			{
				variant->args[cur_pos] = (void *)malloc(sizeof(double));
				if(variant->args[cur_pos] == NULL) {
					return FALSE;
				}

				*((double *)(variant->args[cur_pos])) = *((double *)value);
				variant->type[cur_pos] = 'd';
				variant->n_args ++;
			}
			break;
		case 'a':
			{
				if(cur_pos >= VARIANT_MAX_ARGS-1) {
					return FALSE;
				}

				if(array_len == 0) {
					variant->args[cur_pos] = (void *)0;
				}else {
					variant->args[cur_pos] = (void *) malloc(array_len);
					if (variant->args[cur_pos] == NULL ) {
						return FALSE;
					}
					memcpy(variant->args[cur_pos], (char *) value, array_len);
				}

				variant->type[cur_pos] = 'a';
				//-------分割线
				variant->args[cur_pos+1] = (void *) malloc(sizeof(unsigned int));
				if (variant->args[cur_pos+1] == NULL ) {
					free(variant->args[cur_pos]);
					return FALSE;
				}

				*((unsigned int *) (variant->args[cur_pos+1])) = array_len;
				variant->type[cur_pos+1] = 'u';
				variant->n_args += 2;
			}
			break;
		default:
			{
				return FALSE;
			}
			break;
	}
	return TRUE;
}

/**
 * @brief get variant members.
 * @Warning parameters int/unsigned int/double must be have invalid memery
 * must not use the string get from at_variant after at_variant free.
 */
dbus_bool_t at_variant_get(const at_variant *value, const char *format_string, ...)
{
	int i;
	va_list ap;

	va_start(ap, format_string);
	for(i=0; i<value->n_args; i++) {
		switch(format_string[i]) {
			case 'i':
				{
					int *arg_i;
					arg_i = va_arg(ap, int *);
					if(value->type[i] != format_string[i]) {
						goto ERR;
					}
					*arg_i = *((int *)value->args[i]);
				}
				break;
			case 's':
				{
					char **arg_s;
					arg_s = va_arg(ap, char **);
					if(value->type[i] != format_string[i]) {
						goto ERR;
					}
					*arg_s = (char *)value->args[i];
				}
				break;
			case 'u':
				{
					unsigned int *arg_u;
					arg_u = va_arg(ap, unsigned int *);
					if(value->type[i] != format_string[i]) {
						goto ERR;
					}
					*arg_u = *((unsigned int *)value->args[i]);
				}
				break;
			case 'd':
				{
					double *arg_d;
					arg_d = va_arg(ap, double *);
					if(value->type[i] != format_string[i]) {
						goto ERR;
					}
					*arg_d = *((double *)value->args[i]);
				}
				break;
			case 'a':
				{
					char **arg_s;
					unsigned int *arg_u;

					arg_s = va_arg(ap, char **);
					arg_u = va_arg(ap, unsigned int *);

					if (value->type[i] != format_string[i]) {
						goto ERR;
					}
					*arg_s = (char *) value->args[i];
					i++;
					//------分割线
					if (value->type[i] != format_string[i]) {
						goto ERR;
					}
					*arg_u = *((unsigned int *) value->args[i]);
				}
				break;
			default:
				{
					goto ERR;
				}
				break;
		}
	}
	va_end(ap);
	return TRUE;

ERR:
	va_end(ap);
	return FALSE;
}

void at_proxy_free(at_proxy *proxy)
{
	if(proxy) {
		free(proxy->destination);
		free(proxy->object_path);
		free(proxy->interface);
		free(proxy);
	}
}

at_proxy *at_proxy_new(char *destination, char *object_path, char *interface)
{
	at_proxy *proxy;
	int size;

	proxy = (at_proxy *)malloc(sizeof(at_proxy));
	if(proxy == NULL) {
		return NULL;
	}

	size = strlen(destination);
	proxy->destination = (char *)malloc(size + 1);
	if(proxy->destination == NULL) {
		at_proxy_free(proxy);
		return NULL;
	}
	memset(proxy->destination, 0, size + 1);
	strncpy(proxy->destination, destination, size);

	size = strlen(object_path);
	proxy->object_path = (char *)malloc(size + 1);
	if(proxy->object_path == NULL) {
		at_proxy_free(proxy);
		return NULL;
	}
	memset(proxy->object_path, 0, size + 1);
	strncpy(proxy->object_path, object_path, size);

	size = strlen(interface);
	proxy->interface = (char *)malloc(size + 1);
	if(proxy->interface == NULL) {
		at_proxy_free(proxy);
		return NULL;
	}
	memset(proxy->interface, 0, size + 1);
	strncpy(proxy->interface, interface, size);

	return proxy;
}

static dbus_bool_t append_message_from_variant(DBusMessage *message, at_variant *variant)
{
	int i;

	DBusMessageIter	iter;
	DBusMessageIter array;

	dbus_message_iter_init_append(message, &iter);

	for(i=0; i<variant->n_args; i++) {
		switch(variant->type[i]) {
			dbus_bool_t ret;
			case 'i':
				{
					ret = dbus_message_iter_append_basic(&iter,
							DBUS_TYPE_INT32,
							(int *)(variant->args[i]));
					if(!ret) {
						return FALSE;
					}
				}
				break;
			case 's':
				{
					ret = dbus_message_iter_append_basic(&iter,
							DBUS_TYPE_STRING,
							&(variant->args[i]));
					if(!ret) {
						return FALSE;
					}
				}
				break;
			case 'u':
				{
					ret = dbus_message_iter_append_basic(&iter,
							DBUS_TYPE_UINT32,
							variant->args[i]);
					if(!ret) {
						return FALSE;
					}
				}
				break;
			case 'd':
				{
					ret = dbus_message_iter_append_basic(&iter,
							DBUS_TYPE_DOUBLE,
							variant->args[i]);
					if(!ret) {
						return FALSE;
					}
				}
				break;
			case 'a':
				{
					char buf[2];
					buf[0] = DBUS_TYPE_BYTE;
					buf[1] = '\0';
					if (!dbus_message_iter_open_container (&iter,
							DBUS_TYPE_ARRAY,
							buf,
							&array)) {
						return FALSE;
					}
					if (!dbus_message_iter_append_fixed_array (&array,
							DBUS_TYPE_BYTE,
							&(variant->args[i]),
							*((unsigned int *)variant->args[i+1]))) {
						dbus_message_iter_abandon_container (&iter, &array);
						return FALSE;
					}
					if (!dbus_message_iter_close_container(&iter, &array)) {
						return FALSE;
					}
					i++;
				}
				break;
		}
	}
	return TRUE;
}

at_variant *get_variant_from_message(DBusMessage *message)
{
	DBusMessageIter iter;
	int current_type;
	at_variant *variant;

	//DBusMessageIter contains no allocated memory; it need not be freed, and can be copied by assignment or memcpy().
	if (!dbus_message_iter_init(message, &iter)) {
		return NULL;
	}

	if(!(variant = at_variant_new(""))) {
		return NULL;
	}

	while ((current_type = dbus_message_iter_get_arg_type(&iter)) != DBUS_TYPE_INVALID) {
		switch(current_type) {
			case DBUS_TYPE_INT32:
			{
				int value_i;
				dbus_message_iter_get_basic(&iter, &value_i);
				if(!at_variant_append_args(variant, 'i', &value_i, 0)) {
					goto ERR;
				}
			}
			break;
			case DBUS_TYPE_STRING:
			{
				char *value_s;
				dbus_message_iter_get_basic(&iter, &value_s);
				if(!at_variant_append_args(variant, 's', value_s, 0)) {
					goto ERR;
				}
			}
			break;
			case DBUS_TYPE_UINT32:
			{
				unsigned int value_u;
				dbus_message_iter_get_basic(&iter, &value_u);
				if(!at_variant_append_args(variant, 'u', &value_u, 0)) {
					goto ERR;
				}
			}
			break;
			case DBUS_TYPE_DOUBLE:
			{
				double value_d;
				dbus_message_iter_get_basic(&iter, &value_d);
				if(!at_variant_append_args(variant, 'd', &value_d, 0)) {
					goto ERR;
				}
			}
			break;
			case DBUS_TYPE_ARRAY:
			{
				int v_array_len;
				char *v_array;
				DBusMessageIter sub;
				int current_type;

				dbus_message_iter_recurse(&iter, &sub);

				current_type = dbus_message_iter_get_arg_type (&sub);
				if(dbus_type_is_fixed(current_type)) {
					dbus_message_iter_get_fixed_array(&sub, &v_array, &v_array_len);
					if (!at_variant_append_args(variant, 'a', v_array, (unsigned int)v_array_len)) {
						goto ERR;
					}
				} else {
					if (!at_variant_append_args(variant, 'a', NULL, 0)) {
						goto ERR;
					}
				}
			}
			break;
		}

		dbus_message_iter_next (&iter);
	}

	return variant;
ERR:
	at_variant_free(variant);
	return NULL;
}

/**
 * @brief dbus call sync.
 * @param[in] conn dbus connection.
 * @param[in] proxy the interface proxy.
 * @param[in] method_name method name.
 * @param[in] parameters the parameters, NULL if not parameters.
 * @param[out] error will be set when error occur then error must be free usr dbus_error_free().
 * @return NULL if error occur, else you must free the at_variant.
 */
at_variant *at_dbus_call_sync(DBusConnection *conn,
		at_proxy *proxy,
		const char *method_name,
		at_variant *parameters,
		DBusError *error)
{
	DBusMessage *message = NULL, *reply = NULL;
	at_variant *variant = NULL;

	if(!conn || !proxy || !method_name) {
		return NULL;
	}
	message = dbus_message_new_method_call(proxy->destination,
			proxy->object_path,
			proxy->interface,
			method_name);
	if(message == NULL) {
		goto ERR;
	}

	if(parameters) {
		if(!append_message_from_variant(message, parameters)) {
			goto ERR;
		}
	}

    if(sync_mutex)
        pthread_mutex_lock(sync_mutex);
    reply = dbus_connection_send_with_reply_and_block(conn, message, 2000, error);
    if(sync_mutex)
        pthread_mutex_unlock(sync_mutex);

	if(dbus_error_is_set(error)) {
		goto ERR;
	}
	variant = get_variant_from_message(reply);
	dbus_message_unref(reply);
	dbus_message_unref(message);
	return variant;

ERR:
	if(message) {
		dbus_message_unref(message);
	}
	if(reply) {
		dbus_message_unref(reply);
	}

	if(!dbus_error_is_set(error)) {
		dbus_set_error_const(error, DBUS_ERROR_FAILED, "call sync error");
	}
	return NULL;
}

static dbus_bool_t __at_dbus_connection_send(DBusConnection *conn, DBusMessage *message, dbus_uint32_t *serial)
{
    dbus_bool_t ret;

    if(sync_mutex)
        pthread_mutex_lock(sync_mutex);

    ret = dbus_connection_send(conn, message, serial);
    if(queue_max_size <= 0)
        goto END;

    long cur_size = dbus_connection_get_outgoing_size(conn);
    if(cur_size > queue_max_size)
    {
        dbus_connection_flush(conn);
    }
END:
    if(sync_mutex)
        pthread_mutex_unlock(sync_mutex);
    return ret;
}

void at_dbus_call_async2(DBusConnection *conn,
        at_proxy *proxy,
        const char *method_name,
        at_variant *parameters,
        DBusPendingCallNotifyFunction callback,
        void *user_data,
        DBusFreeFunction free_user_data)
{
	dbus_bool_t ret = FALSE;
    DBusPendingCall *pc;
	DBusMessage *message;
	if (!conn || !proxy || !method_name || !callback) {
		return;
	}

	message = dbus_message_new_method_call(proxy->destination,
			proxy->object_path,
			proxy->interface,
			method_name);
	if(message == NULL) {
        return;
	}

	if(parameters) {
		if(!append_message_from_variant(message, parameters)) {
			goto OUT;
		}
	}

    if(sync_mutex)
        pthread_mutex_lock(sync_mutex);
    ret = dbus_connection_send_with_reply(conn, message, &pc, -1);
    //dbus_connection_flush(conn);

    if(ret == FALSE || pc == NULL)
        goto OUT_UNLOCK;

    if(dbus_pending_call_get_completed (pc))
    {
        callback(pc, user_data);
        if(free_user_data)
            free_user_data(user_data);
        goto OUT_UNLOCK;
    }

    ret = dbus_pending_call_set_notify(pc, callback, user_data, free_user_data);
    if(ret == FALSE)
    {
        PJ_LOG(1, (THIS_FILE, "OOM\n"));
        goto OUT_UNLOCK;
    }
OUT_UNLOCK:
    if(sync_mutex)
        pthread_mutex_unlock(sync_mutex);
OUT:
	if(message) {
		dbus_message_unref(message);
	}
}


void at_dbus_call_async(DBusConnection *conn,
        at_proxy *proxy,
        const char *method_name,
        at_variant *parameters,
        DBusPendingCallNotifyFunction callback,
        void *user_data)
{
    at_dbus_call_async2(conn, proxy, method_name, parameters, callback, user_data, NULL);
}

/**
 * @brief dbus call async.
 * @param[in] conn dbus connection.
 * @param[in] proxy the interface proxy.
 * @param[in] method_name method name.
 * @param[in] parameters NULL if not parameters.
 */
void at_dbus_call(DBusConnection *conn,
		at_proxy *proxy,
		const char *method_name,
		at_variant *parameters)
{
	DBusMessage *message;
	if (!conn || !proxy || !method_name) {
		return;
	}

	message = dbus_message_new_method_call(proxy->destination,
			proxy->object_path,
			proxy->interface,
			method_name);
	if(message == NULL) {
		goto OUT;
	}
	dbus_message_set_no_reply (message, TRUE);

	if(parameters) {
		if(!append_message_from_variant(message, parameters)) {
			goto OUT;
		}
	}

	__at_dbus_connection_send(conn, message, NULL);

OUT:
	if(message) {
		dbus_message_unref(message);
	}
}

void at_dbus_add_signal_match(DBusConnection* conn, const char *destination)
{
	DBusError err;
	char rule[512] = {0};
	char *rule_ptr = rule;

	dbus_error_init(&err);

	snprintf(rule, 512, "type='signal',sender='%s'", destination);
	dbus_bus_add_match(conn, rule_ptr, &err);
	if (dbus_error_is_set(&err)) {
		printf("Match Error (%s)\n",err.message);
		dbus_error_free(&err);
	}
}

void at_dbus_remove_signal_match(DBusConnection* conn, const char *destination)
{
	DBusError err;
	char rule[512] = {0};
	char *rule_ptr = rule;

	dbus_error_init(&err);

	snprintf(rule, 512, "type='signal',sender='%s'", destination);
	dbus_bus_remove_match(conn, rule_ptr, &err);
	if (dbus_error_is_set(&err)) {
		printf("Match Error (%s)\n", err.message);
		dbus_error_free(&err);
	}
}

/**
 * @brief send a signal.
 * @param[in] parameters NULL if not parameters.
 */
dbus_bool_t at_dbus_send_signal(DBusConnection *conn,
		const char *obj_path,
		const char *interface,
		const char *signal_name,
		at_variant *parameters)
{
	DBusMessage *message = NULL;
	dbus_bool_t ret = FALSE;

	message = dbus_message_new_signal(obj_path, interface, signal_name);
	if(message == NULL) {
		goto OUT;
	}

	if(parameters) {
		if(!append_message_from_variant(message, parameters)) {
			goto OUT;
		}
	}

	ret = __at_dbus_connection_send(conn, message, NULL);

OUT:
	if(message) {
		dbus_message_unref(message);
	}
	return ret;
}

/**
 * @brief response success.
 * @param[in] message the message receive from caller.
 */
dbus_bool_t at_dbus_send_message_response_success(DBusConnection *conn,
		             DBusMessage *message, at_variant *reply)
{
	dbus_bool_t ret = FALSE;
	DBusMessage *msg = NULL;

	msg = dbus_message_new_method_return(message);
	if(msg == NULL) {
		goto OUT;
	}

	if(reply) {
		if(!append_message_from_variant(msg, reply)) {
			goto OUT;
		}
	}

	ret = __at_dbus_connection_send(conn, msg, NULL);

OUT:
	if(msg) {
		dbus_message_unref(msg);
	}
	return ret;
}

/**
 * @brief response error.
 * @param[in] error must be dbus error code.http://dbus.freedesktop.org/doc/api/html/group__DBusProtocol.html
 * @param[in] text error message.
 */
dbus_bool_t at_dbus_send_message_response_error(DBusConnection *conn,
		DBusMessage *message,
		const char *error,
		const char *text)
{
	dbus_bool_t ret = FALSE;
	DBusMessage *msg = NULL;

	msg = dbus_message_new_error(message, error, text);
	if(msg == NULL) {
		return FALSE;
	}

	ret = __at_dbus_connection_send(conn, msg, NULL);

	dbus_message_unref(msg);
	return ret;
}
